module vlib.ev.dns.Server ;

import vlib.Types ;


alias vEvents!(vDns_Domain, 8)	DnsCbType ;

static struct vAddr4 {
	version (freebsd) {
		ubyte sin_len;
		ubyte _family	= AddressFamily.INET;
	}   else  {
		ushort _family	= AddressFamily.INET;
	}
	ushort	_port ;
	ubyte[4]	_ip ;
	char[8] sin_zero = 0 ;
}

struct vDns {
	alias typeof(*this)	This ;
	alias typeof(this)	pThis ;
	
	ev_idle				idle_watcher ;
	version( Use_Lock)
		Mutex				mux ;
	ev_loop_t*				loop ;
	int					query_count ;
	int					max_try 	= 4 ;
	double				server_idle_timeout 	= 8 ;
	vDns_Server*[]			servers ;
	vDns_Domain*[char[]]	domains ;
	vDns_Request*[int]		requests ;
	
	void Init(ev_loop_t* _loop) {
		version( Use_Lock) {
			mux		= new Mutex ;
		}
		loop		= _loop ;
		idle_watcher.data	= this ;
		ev_idle_init(&idle_watcher, &on_Idle) ;
	}
	
	bool AddServer(char[] ip, short port) {
		ubyte[4] _ip ;
		if( !isIp(ip, _ip ) ) {
			return false ;
		}
		version( Use_Lock) {
			mux.lock() ;
			scope(exit) mux.unlock() ;
		}
		servers	~= vDns_Server.New(this, ip, port) ;
		return false;
	}
	
	private vDns_Domain* GetByName(char[] domain) {
		vDns_Domain*		_this ;
		vDns_Domain**		ptr	= domain in domains ;
		if( ptr is null ) {
			_this	= new vDns_Domain ;
			_this.name		= domain.dup ;
			domains[ domain ]	= _this ;
			domains.rehash ;
		} else {
			_this	= *ptr ;
		}
		return	_this ;
	}

	int Query(char[] domain, DnsCbType.CallBack cb, void* ctx, ubyte timeout = 1 ) {
		vDns_Domain	_scope ;
		if( isIp(domain, _scope.ip) ) {
			cb(&_scope, ctx) ;
			log!(__FILE__,__LINE__)("ip");
			return 0 ;
		}
		version( Use_Lock) {
			mux.lock() ;
			scope(exit) mux.unlock() ;
		}
		vDns_Domain*	_this 	= GetByName(domain) ;
		if( _this.isCached ) {
			cb(_this, ctx) ;
			// log!(__FILE__,__LINE__)("Cached");
			return 0 ;
		}
		
		if( servers.length is 0 ) {
			_this._errno	= vDns_Domain.Errno.NoServer;
			log!(__FILE__,__LINE__)("no server");
			cb(_this, ctx) ;
			return 0 ;
		}
		
		if(_this.req is null ) {
			_this.req	= vDns_Request.New(this, _this, ++query_count, timeout );
			
			//  put req to send queue, 
			requests[ _this.req.query_id ]	= _this.req ;
			requests.rehash ;
		}
		_this.req.callbacks.on(cb, ctx) ;
		
		//start idle
		ev_idle_start(loop, &idle_watcher);
		
		// server.send( _this.req ) ;
		return _this.req.query_id ;
	}
	
	bool Cancel(int id, DnsCbType.CallBack cb, void* ctx) {
		version( Use_Lock) {
			mux.lock() ;
			scope(exit) mux.unlock() ;
		}
		vDns_Request** ptr	= id in requests ;
		if( ptr is null ) {
			return false ;
		}
		vDns_Request* req	= *ptr ;
		return req.callbacks.un(cb, ctx) ;
	}
	
	static bool isIp(char[] ip, ubyte[] ipv) {
		if( ipv is null || ipv.length < 4 ) {
			return false ;
		}
		static bool CheckNumber(char[] ip , ref int i , ref ubyte ipv ) {
			int j	= i ;
			for( ; j < ip.length ; j ++) {
				if( ip[j] < '0' || ip[j] > '9'  ) {
					break;
				}
			}
			int l	= j - i;
			if( l > 3 ) {
				return false ;
			}
			int v	= 0 ;
			for ( int k = 0 ; k  < l; k++) {
				ubyte _v	= ip[ i + l - k -1 ] - '0' ;
				if( k is 0 ){
					v	+= _v ;
				}else if( k is 1 ){
					v	+= _v * 10 ;
				}else if( k is 2 ){
					v	+= _v * 100 ;
				}
			}
			if( v > 255 ) {
				return false ;
			}
			ipv	= v ; 
			i	= j ;
			return  true ;
		}
		int i	=  0 ;
		if( !CheckNumber(ip, i, ipv[0] )  || i >=ip.length || ip[i++] !is '.'  ){
			return false ;
		}
		if( !CheckNumber(ip, i, ipv[1] )  || i >=ip.length || ip[i++] !is '.'  ){
			return false ;
		}
		if( !CheckNumber(ip, i, ipv[2] )  || i >=ip.length || ip[i++] !is '.'  ){
			return false ;
		}
		if( !CheckNumber(ip, i, ipv[3] ) ){
			return false ;
		}
		return ip.length is i  ;
	}
	
	static extern(C) void on_Idle(ev_loop_t* loop, ev_idle* w,  int ev) {
		pThis	_this 	= cast(pThis) w.data ;
		
		version( Use_Lock) {
			_this.mux.lock() ;
			scope(exit) _this.mux.unlock() ;
		}
		assert( _this.servers.length > 0 ) ;
		// write quene
		int[]  removed_req ;
		foreach( int query_id , req; _this.requests ) {
			assert( req.query_id is query_id ) ;
			
			switch ( req._state ) {
				case vDns_Request.State.Sending:
					if( req.try_time < 5 ) {
						// log!(__FILE__,__LINE__)("send: {} ", req.try_time);
						int server_i	=  req.try_time % _this.servers.length  ;
						req.try_time++ ;
						_this.servers[server_i].send(req) ;
						req._state		= vDns_Request.State.Reading ;
						break ;
					}
					req.domain._errno	= vDns_Domain.Errno.NoTime ;
				case vDns_Request.State.Calling:
				case vDns_Request.State.Deleting:
					ev_timer_stop(_this.loop, &req.timer_watcher) ;
					req.callbacks.fire(req.domain) ;
					req.domain.req	= null ;
					removed_req	~= query_id ;
					break ;
				case vDns_Request.State.Reading:
					break ;
				default:
					log!(__FILE__, __LINE__)( "{}:{}:{}", query_id , req.domain.name, req._state);
			}
		}
		foreach(query_id; removed_req) {
			_this.requests.remove(query_id) ;
		}
		_this.requests.rehash ;
		if( _this.requests.length is 0 ) {
			// stop read write io
		}
		ev_idle_stop(_this.loop, &_this.idle_watcher) ;
	}
}

struct vDns_Server {
	alias typeof(*this)	This;
	alias typeof(this)	pThis;
	
	private {
		vDns* 			dns_base ;
		IPv4Address		dns_addr ;
		
		bool				isWoking ;
		Berkeley			dns_soc ;
		int				soc_fd ;
		ev_io			read_watcher ;
		version(DNS_TCP) {
			ev_io		write_watcher ;
		}
		ev_timer			timer_watcher ;
	}
	
	static package pThis New(vDns* dns_base, char[] ip, short port) {
		pThis		_this	= new This ;
		if( port < 53 ) {
			port	= 53 ;
		}
		_this.dns_base	= dns_base ;
		_this.dns_addr	= new InternetAddress(ip, 53 ) ;
		
		_this.read_watcher.data	= _this ;
		version(DNS_TCP) {
			_this.write_watcher.data	= _this ;
		}
		_this.timer_watcher.data	= _this ;
		
		ev_timer_init(&_this.timer_watcher, &onTimeout, _this.dns_base.server_idle_timeout ,   _this.dns_base.server_idle_timeout  ) ;
		
		return _this ;
	}
	
	private void start() {
		if( !isWoking ) {
			dns_soc.open(AddressFamily.INET, SocketType.DGRAM, ProtocolType.IP) ;
			dns_soc.blocking( false ) ;
			soc_fd = osf_open(cast(void*) dns_soc.handle);
			ev_io_init(&read_watcher, &onDnsRead, soc_fd, EV_READ);
			version(DNS_TCP) {
				ev_io_init(&write_watcher, &onDnsWrite, soc_fd, EV_READ);
			}
			isWoking	= true ;
		}
		ev_timer_start( dns_base.loop, &timer_watcher);
	}
	
	private void stop() {
		if( isWoking ) {
			ev_io_stop(dns_base.loop, &read_watcher);
			version(DNS_TCP) {
				ev_io_stop(dns_base.loop, &write_watcher);
			}
			ev_timer_stop(dns_base.loop, &timer_watcher);
			int ret	= osf_close(soc_fd);
			if( ret !is 0 ){
				log!(__FILE__,__LINE__)("osf_close:{} error:{}, sys:{}", ret  , .errno, SysError.lastMsg );
			}
			dns_soc.detach ;
			isWoking	= false ;
		}
	}
	
	
	private void send(vDns_Request* req) in{
		assert(req !is null );
		assert(dns_base !is null );
		assert(dns_base.loop !is null );
	} body {
		this.start ;
		
		req.server		= this ;
		
		DNS_Query	dq ;
		ubyte[1500]	tmp ;
		ubyte[] _tmp	= dq.Query_A(tmp, req.domain. name , req.query_id ) ;
		
		int i		= dns_soc.sendTo( _tmp, SocketFlags.NONE, dns_addr);
		assert( i is _tmp.length );
		
		version(DNS_TCP) {
			assert( write_watcher.fd > 0);
			ev_io_start(dns_base.loop, &write_watcher) ;
		}
		
		assert( read_watcher.fd > 0);
		ev_io_start(dns_base.loop, &read_watcher) ;
		ev_timer_start(dns_base.loop, &req.timer_watcher) ;
	}

	static extern(C) void onDnsRead(ev_loop_t* loop, ev_io* w, int ev) {
		pThis	_this	= cast(pThis) w.data ;
		
		assert(_this !is null);
		assert(_this.dns_base !is null);
		
		ubyte[2048]	tmp ;
		int	i		= _this.dns_soc.receiveFrom(tmp, SocketFlags.NONE, _this.dns_addr);
		DNS_Query	dq ;
		bool	is_done	= dq.load(tmp[0..i] ) ;
		version( Use_Lock) {
			_this.dns_base.mux.lock() ;
			scope(exit) _this.dns_base.mux.unlock() ;
		}
		
		vDns_Request** _reqz	= dq.h.ID  in _this.dns_base.requests ;
		if( _reqz is null ) {
			return ;
		}
		vDns_Request* req	= *_reqz ;
		assert( req._state is vDns_Request.State.Reading );
		if( is_done ) {
			int len	= dq.h.ANCOUNT ;
			is_done	= false ;
			int	ttl 	= short.max ;
			ubyte* 	ipz	= &req.domain.ip[0] ;
			for( int ai = 0 ; ai < len; ai++) {
				if( ns_t_a is dq.an[ai].type ) {
					is_done	= true ;
					ipz[0..4]	= dq.an[ai].value.A.addr ;
					ipz	+=	10 ;
					if( dq.an[ai].ttl < ttl ) {
						ttl	= dq.an[ai].ttl  ;
					}
				}
			}
			req.domain.expire	= ttl + Clock.now.unix.seconds ;
		} 
		if( is_done ) {
			req._state	= vDns_Request.State.Calling ;
			// log!(__FILE__,__LINE__)(" name :{}, ttl:{}, ip:{}", req.domain.name, req.domain.expire , req.domain.ip ) ;
		} else {
			if( req.try_time > _this.dns_base.max_try ) {
				req.domain._errno	= vDns_Domain.Errno.BadReply ;
				req._state	= vDns_Request.State.Deleting ;
			} else {
				req._state	= vDns_Request.State.Sending ;
			}
		}
		ev_idle_start(_this.dns_base.loop, &_this.dns_base.idle_watcher);
	}
	
	static extern(C)  void onTimeout(ev_loop_t* loop, ev_timer* w, int revents){
		pThis	_this 	= cast(pThis) w.data ;
		assert(_this !is null);
		_this.stop ;
	}

}

struct vDns_Domain {
	alias typeof(*this)	This;
	alias typeof(this)	pThis;
	enum Errno {
		None ,
		NoServer  , 
		BadReply ,
		NoTime ,
	}
	private {
		char[]			name ;
		long				expire ;
		Errno			_errno ;
		vDns_Request*		req ;
	}
	ubyte[32] 	ip ;
	
	bool isCached() {
		int delta	= expire - Clock.now.unix.seconds ;
		if( delta < -3600 ) {
			return false ;
		}
		if( req !is null ) {
			return false ;
		}
		return true ;
	}
	
	int errno(){
		return _errno ;
	}
}

struct vDns_Request {
	alias typeof(*this)	This ;
	alias typeof(this)	pThis ;
	enum State {
		Sending ,
		Reading ,
		Calling ,
		Deleting ,
	}
	
	private {
		vDns* 			base ;
		vDns_Server*		server ;
		vDns_Domain*		domain ;
		int				query_id ;
		int				try_time ;
		State			_state ;
		DnsCbType		callbacks ;
		ev_timer			timer_watcher ;
	}
	
	
	static pThis New(vDns* base, vDns_Domain* _domain,  int _query_id, ubyte timeout) {
		pThis	_this 	= new This ;
		_this.query_id		= _query_id ;
		_this.base			= base ;
		_this.domain		= _domain ;
		_this._state		= State.Sending ;
		
		_this.timer_watcher.data	= _this;
		ev_timer_init(&_this.timer_watcher, &onTimeout , timeout,  timeout) ;
		
		assert( _this.domain.req is null);
		_this.domain.req	= _this ;
		
		return _this;
	}
	
	static extern(C)  void onTimeout(ev_loop_t* loop, ev_timer* w, int revents){
		 // log!(__FILE__,__LINE__)(" request timeout");
		pThis	_this 	= cast(pThis) w.data ;
		assert( _this !is null );
		assert( _this.base !is null );
		assert( _this.base.loop !is null );
		assert( _this.server !is null );
		assert( _this.domain !is null );
		assert( _this.query_id !is 0 );
		_this._state = State.Sending ;
		ev_idle_start(_this.base.loop, &_this.base.idle_watcher);
	}
	
}

